"""
爬虫程序
读取数据库中未爬取的文件，在天眼查和企查查上搜索，并获取公司的各项信息，保存到爬虫结果文件。
如果爬取失败，保存日志到日志文件，后续爬取时不会再爬取此公司。
连续多次爬取失败，会判定为被反爬了，此时程序会暂停等待处理。
一般是天眼查出了验证码，需要在浏览器访问天眼查的页面，输入图形验证码，再回到爬虫，输入任意字符，继续爬取。
"""

import csv
import sqlite3
import time
from datetime import datetime, timedelta

import pandas as pd
import requests
from requests import HTTPError
from bs4 import BeautifulSoup

from config import sql_path, table_name, tmp_path, \
    tyc_search, qcc_search, t_headers, q_headers, log_path


# 日志列表
log = []


def legal_company_name(company_name):
    """让公司名称变得合法（替换掉英文括号，其他规则待添加）

    :param company_name: 初始的公司名称
    :return: 合法的公司名称
    """
    return company_name.replace("(", "（").replace(")", "）")


def get_tyc_detail_link(company_name):
    """获取天眼查上公司的详情页链接

    :param company_name: 正确的公司名称
    :return: 该公司的详情页链接，没有匹配的公司则返回 None (当搜索页为空或者没有匹配的企业)
    """
    response = requests.get(tyc_search.format(company_name), headers=t_headers)
    response.raise_for_status()
    response.encoding = 'utf-8'
    tyc_search_bs = BeautifulSoup(response.text, 'lxml')

    # 搜索结果的列表
    items = tyc_search_bs.select(
        "#search_query > div.container-left > div.search-block.header-block-container > div.result-list.sv-search-container > div > div > div.content")
    for item in items:
        # 当前名称标签
        tyc_name = item.select("div.header > a")

        # 曾用名标签
        old_name = item.select("div.match.row > div")

        # 分别判断是否非空以及相等
        if (tyc_name and tyc_name[0].text == company_name) \
                or (old_name and old_name[0].text == f'历史名称：{company_name}'):
            return item.select("div.header > a")[0]['href']


def get_tyc_detail_info(tyc_detail_link):
    """获取天眼查上企业的具体信息

    :param tyc_detail_link: 企业在天眼查上的详情页
    :return: 各项数据，详情请见返回语句
    """

    response = requests.get(tyc_detail_link, headers=t_headers)
    response.raise_for_status()
    response.encoding = 'utf-8'
    detail_page = BeautifulSoup(response.text, 'lxml')

    ## 注意！！！以下这坨数据预处理的屎山，改动之前请亲自去网页上实践并理解，并且注意现有的判断条件

    # "最新名称"
    new_name = detail_page.select("#company_web_top > div.box.-company-box > div.content > div.header > h1")[0].text
    print(f"最新名称: {new_name}")

    # "参保人数"
    cb_popu = detail_page.select("#_container_baseInfo > table > tbody > tr:nth-child(8) > td:nth-child(2)")[0].text
    try:
        cb_popu = int(cb_popu)
    except ValueError:
        cb_popu = 0

    # "是否高新技术企业"
    tag_str = detail_page.select("#company_web_top > div.box.-company-box > div.content > div.tag-list-content > div")[0].text
    gaoxin = "是" if tag_str.find("高新") >= 0 else "否"

    # ”是否上市“: 有”上市“字眼，并且不是”非上市“和”未上市“的
    comp_type = detail_page.select("#_container_baseInfo > table > tbody > tr:nth-child(7) > td:nth-child(2)")
    stock = "是" if "上市" in comp_type[0].text and \
                   "非上市" not in comp_type[0].text and \
                   "未上市" not in comp_type[0].text else "否"

    # "融资历史"
    A, Aplus, B, new, finance_nums = False, False, False, "无", "无"
    finance = detail_page.select("#_container_rongzi > table")
    if finance:
        ths = [th.text for th in finance[0].select("thead > tr > th")]
        trs = [{ths[i]: tr.contents[i].text for i in range(len(ths))} for tr in finance[0].select("tbody > tr")]
        z = pd.DataFrame(trs)
        if not z.empty:
            A = any(z.index[z.融资轮次 == 'A轮'] >= 0)
            Aplus = any(z.index[z.融资轮次 == 'A轮'] > 0)
            B = any(z.index[z.融资轮次 == 'B轮'] >= 0)
            new = z['融资轮次'][0]
            finance_nums = "/".join([k for k in z.交易金额])

    # "专利数量"
    patent = detail_page.select("#nav-main-patentCountV4 > span.data-count")
    patent = patent[0].text if patent else 0

    # "软著数量"
    cr = detail_page.select("#nav-main-cpoyRCount > span.data-count")
    cr = cr[0].text if cr else 0

    # "2021年招聘信息"
    recruited = "否"
    recruit_info = detail_page.select("#_container_baipin > table")
    if recruit_info:
        flag = recruit_info[0].select("tbody > tr:nth-child(1) > td:nth-child(2)")
        # 比较日期
        if flag:
            t = flag[0].text
            try:
                if t != "-" and datetime.strptime(t, "%Y-%m-%d") + timedelta(365) < datetime.now():
                    recruited = "是"
            except Exception:
                print(recruit_info)

    # 企业的最新名称, 是否上市, 是否国高新, 参保人数, 是否A轮及以上, 是否A轮以上, 是否B轮及以上, 最近融资轮次, 融资额字符串, 专利数量, 软件著作权数量, 近一年是否有招聘
    return new_name, stock, gaoxin, cb_popu, A, Aplus, B, new, finance_nums, patent, cr, recruited


def get_qcc_detail_link(company_name):
    """获取企查查上公司的详情页链接

    :param company_name: 正确的公司名称
    :return: 该公司的详情页链接，没有匹配的公司则返回 None (当搜索页为空或者没有匹配的企业)
    """

    # 请求-响应
    response = requests.get(qcc_search.format(company_name), headers=q_headers)

    # 如果响应码不是200，抛出HTTPError
    response.raise_for_status()

    # 修改编码规则为UTF8
    response.encoding = 'utf-8'

    # 解析搜索列表页
    qcc_search_bs = BeautifulSoup(response.text, 'lxml')

    # 选择所有企业的详情页超链接
    qcc_detail_links = qcc_search_bs.select(".frtrt > td:nth-child(3) > div:nth-child(1) > a:nth-child(1)")

    # 筛选正确的企业，并返回该企业的详情页超链接
    for qcc_detail_link in qcc_detail_links:
        # 每条超链接对应的企业名称标签
        qcc_name = qcc_detail_link.select("span")
        ## 注意：css选择器如果没有匹配项会返回空列表，请注意判断是否非空！
        if qcc_name and qcc_name[0].text == company_name:
            return qcc_detail_link['href']


def get_tag_dujiaoshou(qcc_detail_link):
    """根据公司的企查查详情页链接，判断公司是否是独角兽企业

    :param qcc_detail_link: 公司的企查查详情页链接
    :return: “是” or “否”(默认)
    """
    # 请求-响应
    response = requests.get(qcc_detail_link, headers=q_headers)

    # 如果响应码不是200，抛出HTTPError
    response.raise_for_status()

    # 修改编码规则为UTF8
    response.encoding = 'utf-8'

    # 解析企业详情页
    qcc_detail_bs = BeautifulSoup(response.text, 'lxml')

    # 获取公司标签
    company_tags = qcc_detail_bs.select("div.tag-cell")

    # 默认独角兽标签为“否”
    dujiaoshou = "否"
    for tag_list in company_tags:
        if "#独角兽企业" in tag_list.text.split()[1:]:
            dujiaoshou = "是"
            break
    return dujiaoshou


def spider(sql_path, table_name, tmp_path):
    """爬虫主程序

    :param source: SQLite文件路径
    :param table_name: SQLite表名称
    :param tmp_path: 中间结果路径
    :return:
    """

    # 主循环
    while True:

        # 打开数据库连接
        with sqlite3.connect(sql_path) as conn:

            # 查询没有爬取的企业名称
            rows = conn.execute(f'select name from {table_name} where done = 0;')
            companys = {e[0] for e in rows.fetchall()}

            # 排除出错的企业名称
            with open(log_path, 'r', encoding='utf8') as f:
                falied_set = {i.split(",")[0] for i in f.read().split("\n")}
            companys = list(companys.difference(falied_set))

            print(f"尚有{len(companys)}条数据未爬取。")

            for u, cn in enumerate(companys, start=1):
                try:
                    time.sleep(0.5)

                    company_name = legal_company_name(cn)  # 公司名称用中文括号
                    print(f"{u}.1 {cn}: 开始爬取")

                    # 获取公司在天眼查的详情页链接
                    tyc_detail_link = get_tyc_detail_link(company_name)
                    if tyc_detail_link is not None:
                        # 获取公司在天眼查的信息
                        tyc_info = get_tyc_detail_info(tyc_detail_link)
                        if tyc_info is not None:
                            # 获取公司在企查查的详情页链接
                            qcc_detail_link = get_qcc_detail_link(company_name)
                            if qcc_detail_link is not None:
                                # 获取公司在企查查的信息（目前只有独角兽企业）
                                dujiaoshou = get_tag_dujiaoshou(qcc_detail_link)

                                # 日志写入文件，并清空日志列表
                                if log:
                                    with open(log_path, 'a', encoding='utf8') as f:
                                        f.write('\n' + '\n'.join(log))
                                log.clear()

                                # 企业名称, 最新名称, 是否独角兽, 公司类型, 是否上市, 是否国高新, 参保人数, A, A以上, B, 融资轮次, 融资额, 专利数量, 软著数量, 近一年是否有招聘, 上年度主营业务收入
                                res = [cn] + list(tyc_info)[0:1] + [dujiaoshou] + list(tyc_info)[1:] + [0]
                                with open(tmp_path, 'a', encoding='utf8', newline="") as f:
                                    f_csv = csv.writer(f)
                                    f_csv.writerow(res)

                                # 爬取成功后，更新数据库中的公司状态
                                conn.execute(f"update {table_name} set done = 1 where name = '{cn}';")
                                conn.commit()
                                print(f"{u}.2 {cn}: 爬取完成")
                            else:
                                print(f"{company_name}: 企查查无此企业")
                                log.append(f"{company_name},企查查无此企业")
                        else:
                            print(f"{cn}: 天眼查信息获取出错")
                            log.append(f"{cn},天眼查信息获取出错")
                    else:
                        print(f"{cn}: 天眼查无此企业")
                        log.append(f"{cn},天眼查无此企业")
                # 被反爬了
                except HTTPError:
                    break
            else:
                # 爬取结束后，跳出主循环
                break
        # 关闭数据库连接后，清空日志列表，并发出提示音
        while log:
            log.pop()
            time.sleep(0.8)
            print("\a")
        input("被反爬了！处理后，请按任意字符继续")

        # 处理后，继续下一轮爬虫，直到爬取结束


if __name__ == '__main__':
    spider(sql_path, table_name, tmp_path)
